import sys
import pygame
import os
import random

# 背景宽高像素大小
W, H = 288, 512
# 刷新率
FPS = 30

# 初始化
pygame.init()
# 窗口名字
pygame.display.set_caption("Flappy Bird")
# 游戏刻
CLOCK = pygame.time.Clock()
# 窗口
SCREEN = pygame.display.set_mode((W, H))

# 图片字典
IMAGES = {}
# 循环加载图片资源
for image in os.listdir("assets/sprites"):
    # splitext返回文件名和拓展名，这里只要文件名
    name = os.path.splitext(image)[0]
    # 拼接资源路径
    path = os.path.join("assets/sprites", image)
    # 加载资源，保存内存地址到字典中，用文件名可以访问对应资源
    IMAGES[name] = pygame.image.load(path)

# 音频字典
SOUNDS = {}
# 循环加载音频资源
for sound in os.listdir("assets/audio"):
    # splitext返回文件名和拓展名，这里只要文件名
    name = os.path.splitext(sound)[0]
    # 拼接资源路径
    path = os.path.join("assets/audio", sound)
    # 加载资源，保存内存地址到字典中，用文件名可以访问对应资源
    SOUNDS[name] = pygame.mixer.Sound(path)

# 地板位置计算
FLOOR_Y = H - IMAGES['floor'].get_height()

FLOOR_GAP = IMAGES['floor'].get_width() - W
FLOOR_X = 0


# 游戏入口
def main():
    while True:
        # 随机白天黑夜背景
        IMAGES['bgpics'] = IMAGES[random.choice(['background-day', 'background-night'])]

        # 随机小鸟颜色
        color = random.choice(['red', 'yellow', 'blue'])
        # 定义小鸟资源列表 0:翅膀上 1:翅膀中 2:翅膀下
        IMAGES['birds'] = [IMAGES[color + 'bird-upflap'], IMAGES[color + 'bird-midflap'],
                           IMAGES[color + 'bird-downflap']]

        # 随机管道颜色
        pipe = IMAGES[random.choice(['pipe-red', 'pipe-green'])]
        # 定义管道资源列表 0:红管道 1:绿管道
        IMAGES['pipes'] = [pipe, pygame.transform.flip(pipe, False, True)]

        # 循环三个场景，导游页，游戏页，结束页
        menu_window()
        game_result = game_window()
        end_window(game_result)


# 导游页
def menu_window():
    # 计算导游页背景图片为居中
    guide_x = (W - IMAGES['guide'].get_width()) / 2
    guide_y = (FLOOR_Y - IMAGES['guide'].get_height()) / 2

    # 小鸟布置在宽度*0.2，高度*0.5的位置
    bird = Bird(W * 0.2, H * 0.5)
    bird.y_vel = 0
    bird.gravity = 0
    bird.rotate = 0

    while True:
        # pygame模块事件循环
        for event in pygame.event.get():
            # 空格后开始
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                return

            # 退出游戏
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        # pygame布置场地
        SCREEN.blit(IMAGES['bgpics'], (0, 0))
        SCREEN.blit(IMAGES['floor'], (0, FLOOR_Y))
        SCREEN.blit(IMAGES['guide'], (guide_x, guide_y))
        SCREEN.blit(bird.image, bird.rect)

        # 更新小鸟
        bird.update()
        # 更新pygame帧
        pygame.display.update()
        CLOCK.tick(FPS)


# 游戏中页面
def game_window():
    global FLOOR_X

    # 小鸟布置在宽度*0.2，高度*0.4的位置
    bird = Bird(W * 0.2, H * 0.4)
    pipe = Pipe(W, H * 0.5, True)

    # 水管距离
    distance = 180
    # 上下水管之间的距离
    pipe_gap = 120
    # 水管数
    n = 4
    pipe_group = pygame.sprite.Group()
    for i in range(n):
        pipe_y = random.randint(int(H * 0.3), int(H * 0.7))
        pipe_up = Pipe(W + i * distance, pipe_y, True)
        pipe_down = Pipe(W + i * distance, pipe_y - pipe_gap, False)
        pipe_group.add(pipe_up)
        pipe_group.add(pipe_down)

    while True:
        # 默认没有拍动翅膀
        flap = False

        for event in pygame.event.get():
            # 拍动翅膀
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                flap = True
                SOUNDS['flap'].play()
            # 退出游戏
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        first_pipe_up = pipe_group.sprites()[0]
        first_pipe_down = pipe_group.sprites()[1]
        if first_pipe_up.rect.right < 0:
            pipe_y = random.randint(int(H * 0.3), int(H * 0.7))
            new_pipe_up = Pipe(first_pipe_up.rect.x + n * distance, pipe_y, True)
            new_pipe_down = Pipe(first_pipe_up.rect.x + n * distance, pipe_y - pipe_gap, False)
            pipe_group.add(new_pipe_up)
            pipe_group.add(new_pipe_down)
            first_pipe_up.kill()
            first_pipe_down.kill()

        pipe_group.update()

        # 天花板/地板碰撞判断是不是死亡了
        if bird.rect.y > FLOOR_Y or bird.rect.y < 0:
            SOUNDS['hit'].play()
            SOUNDS['die'].play()
            # 把小鸟传递出去
            return {'bird': bird}

        # TODO:碰撞检测
        for pipe in pipe_group.sprites():
            right_to_left = max(bird.rect.right, pipe.rect.right) - min(bird.rect.left, pipe.rect.left)
            bottom_to_top = max(bird.rect.bottom, pipe.rect.bottom) - min(bird.rect.top, pipe.rect.top)
            if right_to_left < bird.rect.width + pipe.rect.width and bottom_to_top < bird.rect.height + pipe.rect.height:
                SOUNDS['hit'].play()
                SOUNDS['die'].play()
                result = {'bird':bird}
                return result



        # 让地板往左跑
        FLOOR_X -= 4
        # 如果地板跑出去了，就要把地板 回来
        if FLOOR_X <= - FLOOR_GAP:
            FLOOR_X = 0
        # 把小鸟拍动翅膀的参数传给小鸟类执行
        bird.update(flap=flap)
        # 批量更新水管
        pipe_group.update()

        # pygame布置场地
        SCREEN.blit(IMAGES['bgpics'], (0, 0))
        SCREEN.blit(bird.image, bird.rect)
        # 批量布置水管
        pipe_group.draw(SCREEN)
        SCREEN.blit(IMAGES['floor'], (FLOOR_X, FLOOR_Y))

        # 更新小鸟
        bird.update()
        # 更新pygame帧
        pygame.display.update()
        CLOCK.tick(FPS)


# 游戏结束页面
def end_window(result):
    gameOver_x = (W - IMAGES['gameover'].get_width()) / 2
    gameOver_y = (FLOOR_Y - IMAGES['gameover'].get_height()) / 2

    bird = result['bird']

    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                return

            # 退出游戏
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        bird.go_die()

        # pygame布置场地
        SCREEN.blit(IMAGES['bgpics'], (0, 0))
        SCREEN.blit(IMAGES['gameover'], (gameOver_x, gameOver_y))
        SCREEN.blit(IMAGES['floor'], (0, FLOOR_Y))
        SCREEN.blit(bird.image, bird.rect)

        # 更新pygame帧
        pygame.display.update()
        CLOCK.tick(FPS)


class Bird:
    def __init__(self, x: int, y: int) -> None:
        # 帧列表
        self.frames = [0] * 5 + [1] * 5 + [2] * 5 + [1] * 5
        # 帧索引
        self.idx = 0
        self.images = IMAGES['birds']
        self.image = self.images[self.frames[self.idx]]
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        # 升力,小鸟上升,y轴是减小的
        self.y_vel = -8
        # 最大上升速度
        self.max_y_vel = 8
        # 重力加速度
        self.gravity = 0.5
        # 当前旋转角度
        self.rotate = 45
        # 最大旋转角度
        self.max_rotate = -20
        # 小鸟角度
        self.rotate_vel = -3
        # 拍动翅膀y速度
        self.y_vel_after_flap = -8
        # 拍动翅膀后的小鸟角度(初始的小鸟角度)
        self.rotate_after_flap = 45

    def update(self, flap=False) -> None:
        # 如果翅膀被拍动了
        if flap:
            # 拍动翅膀后，小鸟获得了y的速度
            self.y_vel = self.y_vel_after_flap
            # 拍动翅膀后，小鸟姿态变化了
            self.rotate = self.rotate_after_flap

        self.y_vel = min(self.y_vel + self.gravity, self.max_y_vel)
        self.rect.y += self.y_vel
        self.rotate = max(self.rotate + self.rotate_vel, self.max_rotate)

        # 帧数索引递增
        self.idx += 1
        # 取余等于帧数索引，保证帧数索引一直是0到len(self.frames)这个范围
        self.idx %= len(self.frames)
        # 根据帧索引更新图片
        self.image = self.images[self.frames[self.idx]]
        # 根据小鸟角度调整小鸟图片旋转
        self.image = pygame.transform.rotate(self.image, self.rotate)

    def go_die(self):
        # 如果小鸟死亡时还和地板有距离时，可以执行垂直掉落的逻辑
        if self.rect.y < FLOOR_Y:
            # 小鸟以最大速度掉落
            self.rect.y += self.max_y_vel
            # 垂直掉落时的角度
            self.rotate = -90
            # 设置坠落时的小鸟帧和小鸟角度
            self.image = self.images[self.frames[self.idx]]
            self.image = pygame.transform.rotate(self.image, self.rotate)


# 水管类
class Pipe(pygame.sprite.Sprite):
    def __init__(self, x: int, y: int, upwards: bool) -> None:
        pygame.sprite.Sprite.__init__(self)
        if upwards:
            self.image = IMAGES['pipes'][0]
            self.rect = self.image.get_rect()
            self.rect.x = x
            self.rect.top = y
        else:
            self.image = IMAGES['pipes'][1]
            self.rect = self.image.get_rect()
            self.rect.x = x
            self.rect.bottom = y
        self.x_vel = -2

    def update(self):
        self.rect.x += self.x_vel


main()
